import { Controller, Get, Param, Post, Sse } from "@nestjs/common";
import { SseEnhanceService } from "./sse-enhance.service";
import { TaskProgressStore } from "./task-progress.store";
import { Public } from "src/decorators/is-public.decorator";
import { filter, interval, map, Observable, takeUntil, tap } from "rxjs";
import { TaskProgress, TaskStatus } from "./sse-enhance.type";

@Controller('sse-enhance')
export class SseEnhanceController {
    constructor(
        private readonly sseEnhanceService: SseEnhanceService,
        private readonly taskProgressStore: TaskProgressStore) { }

    @Public()
    @Sse('task/:taskId')
    executeAsyncTask(@Param('taskId') taskId: string): Observable<MessageEvent<TaskProgress>> {

        /**
         * The Flow is:
         * 1. Check task exists, if not, start task.
         * 2. init task
         * 3. execute task runner:
         *    a. simulate async task
         *    b. update progress in store
         * 4. poll progress from store, emit to FE.
         */

        // Check task exists, if not, start task
        if (!this.sseEnhanceService.isTaskExists(taskId)) {
            this.sseEnhanceService.executeTask(taskId);
        }

        /**
            1. use interval to poll progress, watch only, not generate data.
            2. filter null progress, task not initialized.
            3. convert to SSE format, type is consistent with frontend event name.
            4. stop stream when task completed or failed.
         */
        return interval(50).pipe(
            map(() => this.taskProgressStore.getTask(taskId)), // get progress from store
            filter((progress): progress is TaskProgress => progress !== null), // filter invalid progress
            tap((task) => {
                if (task?.status === TaskStatus.COMPLETED || task?.status === TaskStatus.FAILED) {
                    this.taskProgressStore.removeTask(taskId);
                }
            }),
            map((task) => ({
                type: 'progressUpdate', // event type, FE need to listen this event
                data: task,
            } as MessageEvent<TaskProgress>)),
            // stop stream when task completed or failed
            takeUntil(interval(50).pipe(
                filter(() => {
                    const progress = this.taskProgressStore.getTask(taskId);
                    return progress === null;
                })
            ))
        )
    }
}